package demoscene;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 *
 * @author Luis
 */
public class FastPlasma {
    int mSize;
    int mWidth;
    int mHeight;
    int[] mPalette;
    int[] mSinTable;
    int[] mSinTableWidth;
    int[] mSinTableHeight;
    Random mRandom;
    int[] randomPhase;
    
    short[][] mPlasmaBuffer;
    
    int mTime;
      
    int RGB(int r, int g, int b, int a){
        return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
    }
      
    public FastPlasma(int width, int height){
        mRandom = new Random();
        randomPhase = new int[] {mRandom.nextInt(1024), mRandom.nextInt(1024), mRandom.nextInt(1024)};
        
        mWidth = width;
        mHeight = height;
        mSize = width * height;

        mSinTable = new int[256];
        mSinTableWidth = new int[1024];
        mSinTableHeight = new int[1024];
        for (int i = 0 ; i < mSinTable.length ; i++)
            mSinTable[i] = 128 + (int)(127 * Math.sin(i * 2 * Math.PI / mSinTable.length));
        for (int i = 0 ; i < mSinTableWidth.length ; i++)
            mSinTableWidth[i] = mWidth / 2 + (int)(mWidth * Math.sin(i * 2 * Math.PI / mSinTableWidth.length) / 2);
        for (int i = 0 ; i < mSinTableHeight.length ; i++)
            mSinTableHeight[i] = mHeight / 2 + (int)(mHeight * Math.sin(i * 2 * Math.PI / mSinTableHeight.length) / 2);

        mPalette = new int[256];
        for (int i = 0 ; i < mPalette.length ; i++)
            mPalette[i] = RGB(mSinTable[(2 * i) & 0xff], mSinTable[(64 + 2 * i) & 0xff], 0, 255); 
        
        mPlasmaBuffer = new short[3][4 * mWidth * mHeight];
        for (int y = 0 ; y < 2 * mHeight ; y++)
            for (int x = 0 ; x < 2 * mWidth ; x++){        
                mPlasmaBuffer[0][x + y * 2 * mWidth] = (short) mSinTable[(2 * x + mSinTable[y & 0xff]) & 0xff];
                mPlasmaBuffer[1][x + y * 2 * mWidth] = (short) mSinTable[(64 + 2 * y + mSinTable[(64 + x) & 0xff]) & 0xff];
                mPlasmaBuffer[2][x + y * 2 * mWidth] = (short) mSinTable[(x + y +  mSinTable[x & 0xff]) & 0xff];           
            }
    }
       
    public void render(int[] destination, int dt){
        int value;
        int[] delta = {
            (mSinTableWidth[mTime & 0x3ff]) / 2 + 2 * mWidth * mSinTableHeight[(randomPhase[1] + mTime) & 0x3ff],
            (mSinTableWidth[(randomPhase[0] + mTime) & 0x3ff]) + mWidth * mSinTableHeight[(randomPhase[2] + mTime) & 0x3ff],
            (mSinTableWidth[mTime & 0x3ff]) / 2 + 2 * mWidth * mSinTableHeight[mTime & 0x3ff]
        };
        for (int i = 0 ; i < mSize ; i++){
            value = 0;
            for (int j = 0 ; j < 3 ; j++)
                value += mPlasmaBuffer[j][i + delta[j]];
            value /= 3;
            destination[i] = mPalette[value];
        }
        mTime += dt;
    }
    
    
    public static void main(String[] args) {
        int WIDTH = 256;
        int HEIGHT = 256;
        
        //preparamos panel para mostrar el efecto
        JFrame frame = new JFrame();
        JPanel panel = (JPanel)frame.getContentPane();
        panel.setPreferredSize(new Dimension(WIDTH, HEIGHT));  //ajustamos tamaño a WIDTH x HEIGHT
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  //al cerrar el frame -> se cierra la aplicación
        frame.pack();  //compactamos el frame (se ajusta tamaño del frame a contenido)
        frame.setLocationRelativeTo(null);  //frame al centro de la pantalla
        frame.show();  //mostramos el frame        
        
        int[] mBufferEffect = new int[WIDTH * HEIGHT];  //buffer de enteros de WIDTH * HEIGHT elementos en donde se "volcará" el efecto plasma
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);  //imagen
        //creamos la clase que realizará el render del efecto (ver clase "Plasma")
        //pasamos como parámetro el ancho y el alto
        FastPlasma effect = new FastPlasma(WIDTH, HEIGHT);
        
        //blucle de animación        
        while (true){
            effect.render(mBufferEffect, 1);  //calculamos frame y volcamos resultado en buffer temporal
            Graphics g = panel.getGraphics();  //tomamos "canvas" del panel (en donde vamos a dibujar)
            if (g != null){
                image.setRGB(0, 0, WIDTH, HEIGHT, mBufferEffect, 0, WIDTH);  //volcamos buffer sobre imágen temporal
                g.drawImage(image, 0, 0, WIDTH, HEIGHT, null);  //dibujamos imagen temporal sobre el "canvas"
                g.dispose();  //liberamos el "canvas"
            } 
        }
    }     
}
